package com.xiashitech.agent.utils;

// import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.xiashitech.agent.config.StartConfig;

import javax.json.*;
import javax.json.spi.JsonProvider;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Pattern;

public class JsonUtil {
    private final static ObjectMapper om = new ObjectMapper();
    static {
        om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    }

    public static boolean isJson(String jsonString) {
        boolean isValid = false;
        try {
            if (jsonString != null && !"".equals(jsonString.trim())) {
                jsonString = jsonString.trim();
                if (jsonString.startsWith("{") && jsonString.endsWith("}")) {
                    // new ObjectMapper().readTree(jsonString);
                    // JSONObject.toJSONString(jsonString);
                    om.readTree(jsonString);
                    isValid = true;
                } else if (jsonString.startsWith("[") && jsonString.endsWith("]")) {
                    // new ObjectMapper().readTree(jsonString);
                    // JSONObject.toJSONString(jsonString);
                    om.readTree(jsonString);
                    isValid = true;
                }
            }
        } catch (Throwable e) {
        }
        return isValid;
    }

    public static boolean isJsonObj(String jsonString) {
        boolean isValid = false;
        try {
            if (jsonString != null && !"".equals(jsonString.trim())) {
                jsonString = jsonString.trim();
                if (jsonString.startsWith("{") && jsonString.endsWith("}")) {
                    om.readTree(jsonString);
                    isValid = true;
                }
            }
        } catch (Throwable e) {
        }
        return isValid;
    }

    public static boolean isJsonArray(String jsonString) {
        boolean isValid = false;
        try {
            if (jsonString != null && !"".equals(jsonString.trim())) {
                isValid = om.readTree(jsonString.trim()).getNodeType() == JsonNodeType.ARRAY;
            }
        } catch (Throwable e) {
        }
        return isValid;
    }

    /**
     * 获取jsonArray大小
     * @param jsonString
     * @return
     */
    public static int getJsonArraySize(String jsonString) {
        try {
            if (jsonString != null && !"".equals(jsonString.trim())) {
                JsonNode jsonNode = om.readTree(jsonString.trim());
                if (jsonNode.getNodeType() == JsonNodeType.ARRAY) {
                    return jsonNode.size();
                }
            }
        } catch (Throwable e) {
        }
        return 0;
    }

    /**
     * 根据key获取json层级中的字符串值
     * @param jsonString
     * @param key
     * @return
     */
    public static String getStringByKey(String jsonString, String key) {
        if (key == null || "".equals(key)) {
            return jsonString;
        }
        JsonNode node = getNodeByKey(jsonString, key);
        if (node != null) {
            return node.toString();
        }
        return null;
    }

    /**
     * 根据key获取json层级中的node节点
     * @param jsonString
     * @param key
     * @return
     */
    public static JsonNode getNodeByKey(String jsonString, String key) {
        try {
            JsonNode jsonNode = om.readTree(jsonString);
            if (key == null || "".equals(key)) {
                return jsonNode;
            }
            Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                if (Objects.equals(key, entry.getKey())) {
                    return entry.getValue();
                }
            }
        } catch (Exception e) {}
        return null;
    }

    public static void main(String[] args) {
//        String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";
//        System.out.println(isJsonArray(jsonArray));

        String jsonArray = "{\"id\":\"f13f1659-211d-43c0-9c78-e1601bd6e411\",\"name\":\"_productAttribute_category_test_list22\",\"path\":\"/productAttribute/category/test_list22\",\"httpMethod\":[\"POST\"],\"displayName\":\"/productAttribute/category/test_list22\",\"fullyQualifiedName\":\"ApiService.jsh-erp._productAttribute_category_test_list22\",\"description\":\"the api test\",\"keyPath\":\"/a/b/c\",\"version\":0.1,\"updatedAt\":1701772881476,\"updatedBy\":\"anonymous\",\"href\":\"http://localhost:8080/api/v1/apis/f13f1659-211d-43c0-9c78-e1601bd6e411\",\"apiType\":\"Http\",\"requestParams\":[{\"name\":\"pageSize\",\"displayName\":\"pageSize\",\"dataType\":\"Integer\",\"dataTypeDisplay\":\"integer\",\"description\":\"Number of data items in a page\",\"fullyQualifiedName\":\"ApiService.jsh-erp._productAttribute_category_test_list22.requestParams.pageSize\",\"subParams\":[]},{\"name\":\"pageNum\",\"displayName\":\"pageNum\",\"dataType\":\"Integer\",\"dataTypeDisplay\":\"integer\",\"description\":\"The number of the current page in the total page number\",\"fullyQualifiedName\":\"ApiService.jsh-erp._productAttribute_category_test_list22.requestParams.pageNum\",\"subParams\":[]}],\"responseParams\":[{\"name\":\"code\",\"displayName\":\"code\",\"dataType\":\"long\",\"dataTypeDisplay\":\"long\",\"description\":\"Request response coding\",\"fullyQualifiedName\":\"ApiService.jsh-erp._productAttribute_category_test_list22.responseParams.code\",\"subParams\":[]},{\"name\":\"message\",\"displayName\":\"message\",\"dataType\":\"String\",\"dataTypeDisplay\":\"string\",\"description\":\"Meaning of request response coding\",\"fullyQualifiedName\":\"ApiService.jsh-erp._productAttribute_category_test_list22.responseParams.message\",\"subParams\":[]}],\"system\":{\"id\":\"c49ea578-1957-4e4c-91b5-7bc35c4221d6\",\"type\":\"system\",\"name\":\"jsh-erp\",\"fullyQualifiedName\":\"ApiService.jsh-erp\",\"displayName\":\"jsh-erp\",\"deleted\":false,\"href\":\"http://localhost:8080/api/v1/systems/c49ea578-1957-4e4c-91b5-7bc35c4221d6\"},\"service\":{\"id\":\"2e26dc4e-27d2-46c5-a17c-80f78cfdb7c2\",\"type\":\"apiService\",\"name\":\"ApiService\",\"fullyQualifiedName\":\"ApiService\",\"displayName\":\"ApiService\",\"deleted\":false,\"href\":\"http://localhost:8080/api/v1/services/apiServices/2e26dc4e-27d2-46c5-a17c-80f78cfdb7c2\"},\"serviceType\":\"Agent\",\"deleted\":false,\"manualValidation\":false}";
        try {
            HashSet<String> jsonKeys = new HashSet<>();
            JsonNode jsonNode = om.readTree(jsonArray);
            jsonNode.elements().forEachRemaining(node -> {
                if (node.getNodeType() == JsonNodeType.ARRAY) {
                    node.elements().forEachRemaining(arrayNode -> {
                        System.out.println(arrayNode.getNodeType() + ":" + arrayNode.toString());
                    });
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static <T> T convertStringToObject(String jsonString, Class<T> clazz) {
        if (jsonString == null) {
            return null;
        }
        try {
            // return new ObjectMapper().readValue(jsonString, clazz);
            return om.readValue(jsonString, clazz);
            // return JSONObject.parseObject(jsonString, clazz);
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
        return null;
    }

    public static Object getObjectFromByteArray(byte[] bytes) {
        Object obj = null;
        if (bytes != null) {
            String jsonObj = new String(bytes);
            if (JsonUtil.isJson(jsonObj)) {
                obj = JsonUtil.convertStringToObject(jsonObj, Object.class);
            } else {
                try (
                        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
                        ObjectInputStream ois = new ObjectInputStream(bis);
                ) {
                    obj = ois.readObject();
                } catch (Throwable e) {
                    return new String(bytes);
                }
            }
        }
        return obj;
    }

    public static String convertObjectToString(Object object) {
        if (object == null) {
            return null;
        }
        try {
            // fastjson
            return JSONObject.toJSONString(object);
            // 非单例模式，fasterxml
            // return new ObjectMapper().writeValueAsString(object);
            // 单例，fasterxml
            // return om.writeValueAsString(object);
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
        return null;
    }

    public static ObjectNode getJsonNode(Object object) {
        //ObjectNode objectNode = new ObjectMapper().valueToTree(object);
        ObjectNode objectNode = om.valueToTree(object);
        return objectNode;
    }

    public static Map<String, Object> getMapFromInputStream(InputStream inputStream) {
        try {
            // return new ObjectMapper().readValue(inputStream, Map.class);
            return om.readValue(inputStream, Map.class);
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
        return null;
    }

    /**
     * 根据path位置和regular规则，替换oriJson内容，然后返回替换后的json
     *
     * @param oriJson
     * @param path
     * @param regular
     * @return
     */
    public static String jsonPatch(String oriJson, String path, String regular, String replaceSymbol) {
        try (JsonReader jsonReader = Json.createReader(new StringReader(oriJson))) {
            JsonStructure jsonStructure = jsonReader.read();
            List<PathContent> paths = new ArrayList();
            generatePathList(jsonStructure, path, paths);
            JsonPatchBuilder jsonPatchBuilder = Json.createPatchBuilder();
            paths.stream().filter(pathContent -> pathContent.isHasValue()).map(pathContent -> pathContent.getPath()).forEach(fullPath -> {
                try {
                    JsonValue oriJsonValue = jsonStructure.getValue(fullPath);
                    String pathJsonValue = oriJsonValue.toString();
                    if (pathJsonValue.startsWith("\"")) {
                        pathJsonValue = pathJsonValue.substring(1);
                    }
                    if (pathJsonValue.endsWith("\"")) {
                        pathJsonValue = pathJsonValue.substring(0, pathJsonValue.length() - 1);
                    }
                    boolean isNumberWithDot = Pattern.matches("^\\d+(\\.\\d+)", pathJsonValue);
                    boolean isNumberWithNotDot = Pattern.matches("^\\d+", pathJsonValue);
                    pathJsonValue = pathJsonValue.replaceAll(regular, replaceSymbol);
                    switch (oriJsonValue.getValueType()) {
                        case ARRAY:
                            try {
                                if (isNumberWithDot) {
                                    oriJsonValue = JsonProvider.provider().createArrayBuilder().add(Double.valueOf(pathJsonValue)).build();
                                } else if (isNumberWithNotDot) {
                                    oriJsonValue = JsonProvider.provider().createArrayBuilder().add(Long.valueOf(pathJsonValue)).build();
                                } else {
                                    oriJsonValue = JsonProvider.provider().createArrayBuilder().add(pathJsonValue).build();
                                }
                            } catch (Throwable e) {
                                oriJsonValue = JsonProvider.provider().createArrayBuilder().add(pathJsonValue).build();
                            }
                            break;
                        case NUMBER:
                            try {
                                if (isNumberWithNotDot) {
                                    oriJsonValue = JsonProvider.provider().createValue(Long.valueOf(pathJsonValue));
                                } else {
                                    oriJsonValue = JsonProvider.provider().createValue(Double.valueOf(pathJsonValue));
                                }
                            } catch (Throwable e) {
                                oriJsonValue = JsonProvider.provider().createValue(pathJsonValue);
                            }
                            break;
                        default:
                            oriJsonValue = JsonProvider.provider().createValue(pathJsonValue);
                    }
                    jsonPatchBuilder.replace(fullPath, oriJsonValue);
                } catch (Throwable e) {
                    e.printStackTrace();
                    StartConfig.throwableStackPrinter(e);
                }
            });
            JsonStructure applyJsonStructure = jsonPatchBuilder.build().apply(jsonStructure);
            return applyJsonStructure.toString();
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
        return oriJson;
    }

    public static void generatePathList(JsonStructure jsonStructure, String path, List<PathContent> resultList) {
        if (path == null || "".equals(path)) {
            return;
        }
        if (!path.contains("*")) {
            if (resultList.size() == 0) {
                PathContent pathContent = new PathContent();
                pathContent.setHasValue(true);
                pathContent.setPath(path);
                resultList.add(pathContent);
            } else {
                for (int i = 0; i < resultList.size(); i++) {
                    if (resultList.get(i).isHasValue()) {
                        resultList.get(i).setPath(resultList.get(i).getPath() + path);
                    }
                }
            }
            return;
        }
        String arrayPath = path.substring(0, path.indexOf("*"));
        if (arrayPath.endsWith("/")) {
            arrayPath = arrayPath.substring(0, arrayPath.length() - 1);
        }
        String otherPath = path.substring(path.indexOf("*") + 1);
        if (otherPath.endsWith("/")) {
            otherPath = otherPath.substring(0, otherPath.length() - 1);
        }
        if (resultList.size() == 0) {
            PathContent pathContent = new PathContent();
            pathContent.setPath(arrayPath);
            JsonValue oriJsonValue = jsonStructure.getValue(pathContent.getPath());
            if (oriJsonValue.getValueType() == JsonValue.ValueType.ARRAY) {
                JsonArray jsonArray = oriJsonValue.asJsonArray();
                for (int j = 0; j < jsonArray.size(); j++) {
                    PathContent pathArrayItem = new PathContent();
                    pathArrayItem.setHasValue(true);
                    pathArrayItem.setPath(pathContent.getPath() + "/" + j);
                    resultList.add(pathArrayItem);
                }
            }
        } else {
            List<PathContent> pathContentCaches = new ArrayList();
            for (int i = 0; i < resultList.size(); i++) {
                PathContent pathContent = resultList.get(i);
                pathContent.setPath(pathContent.getPath() + arrayPath);
                String pathContentFirst = pathContent.getPath();
                JsonValue oriJsonValue = jsonStructure.getValue(pathContentFirst);
                if (oriJsonValue.getValueType() == JsonValue.ValueType.ARRAY) {
                    JsonArray jsonArray = oriJsonValue.asJsonArray();
                    for (int j = 0; j < jsonArray.size(); j++) {
                        if (j == 0) {
                            pathContent.setPath(pathContentFirst + "/" + 0);
                            continue;
                        }
                        PathContent pathArrayItem = new PathContent();
                        pathArrayItem.setPath(pathContentFirst + "/" + j);
                        pathArrayItem.setHasValue(true);
                        pathContentCaches.add(pathArrayItem);
                    }
                } else if (oriJsonValue.getValueType() == JsonValue.ValueType.NULL) {
                    pathContent.setHasValue(false);
                }
            }
            resultList.addAll(pathContentCaches);
        }
        generatePathList(jsonStructure, otherPath, resultList);
    }

    public static class PathContent {
        String path;
        boolean hasValue;

        public boolean isHasValue() {
            return hasValue;
        }

        public void setHasValue(boolean hasValue) {
            this.hasValue = hasValue;
        }

        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }
    }

    public static void getJsonKeys(String json, Set<String> jsonKeys) {
        try {
            // ObjectMapper jackson = new ObjectMapper();
            JsonNode node = om.readTree(json); // jackson.readTree(json);
            getJsonKeys(node, jsonKeys);
        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
    }

    public static void getJsonKeys(JsonNode node, Set<String> jsonKeys) {
        if (node.isObject()) {
            Iterator<Map.Entry<String, JsonNode>> it = node.fields();
            while (it.hasNext()) {
                Map.Entry<String, JsonNode> entry = it.next();
                jsonKeys.add(entry.getKey());
                getJsonKeys(entry.getValue(), jsonKeys);
            }
        }

        if (node.isArray()) {
            Iterator<JsonNode> it = node.iterator();
            while (it.hasNext()) {
                getJsonKeys(it.next(), jsonKeys);
            }
        }
    }

    /**
     * @param json
     * @param targetDeep
     * @param currentDeep
     * @param index
     * @return
     */
    public static int getRecordSize(String json, int targetDeep, int currentDeep, int index) {
        try {
            // ObjectMapper jackson = new ObjectMapper();
            JsonNode node = om.readTree(json); // jackson.readTree(json);
            getRecordSize(node, targetDeep, currentDeep, index);
        }catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
        return 1;
    }

    public static int getRecordSize(JsonNode node, int targetDeep, int currentDeep, int index) {
        try {
            Iterator<Map.Entry<String, JsonNode>> it = node.fields();
            if (targetDeep <= 1) {
                if (node.isArray()) {
                    return node.size();
                } else {
                    return 1;
                }
            } else if (targetDeep > 1 && targetDeep <= 10) {
                int i = 0;
                if (currentDeep < targetDeep) {
                    while (it.hasNext()) {
                        Map.Entry<String, JsonNode> entry = it.next();
                        // getRecordSize(entry.getValue().toString(), targetDeep, currentDeep + 1, index);
                        getRecordSize(entry.getValue(), targetDeep, currentDeep + 1, index);
                    }
                }
                while (it.hasNext()) {
                    Map.Entry<String, JsonNode> entry = it.next();
                    if(entry.getValue().isArray())
                    {
                        if(i>=index){
                            return entry.getValue().size();
                        }
                        else {
                            i++;
                        }
                    }
                }

            } else if (targetDeep > 10) {
                if (node.isArray()) {
                    return node.size();
                }
                if (node.isObject()) {

                    while (it.hasNext()) {
                        Map.Entry<String, JsonNode> entry = it.next();
                        //    jackson.readTree(entry.getKey());
                        if (entry.getValue().isArray()) {
                            return entry.getValue().size();
                        } else if (entry.getValue().isObject()) {
                            String value = entry.getValue().toString();
                            // getRecordSize(entry.getValue().toString(), targetDeep, currentDeep + 1, index);
                            getRecordSize(entry.getValue(), targetDeep, currentDeep + 1, index);
                        }
                    }
                }
            }

        } catch (Throwable e) {
            StartConfig.throwableStackPrinter(e);
        }
        return 1;
    }


    /**
     * 深度方式遍历对象，检查是否存在循环引用
     * 注意：
     * 1、对于元素很多的对象，如大数组、大Map，可能会很耗时
     * 2、没有深度层级控制，是否会有问题，不确定
     *
     * @param object                需要被遍历的对象
     * @param parentIdentityHashMap 遍历路径上的对象作为key，key存在，表示对象已存在，对比的是对象的 identityHashCode
     * @return 真假，ture 表示存在循环引用，false，表示无循环引用
     */
    public static boolean isCircleReference(Object object, IdentityHashMap<Object, Object> parentIdentityHashMap) {
        if (object == null)
            return false;

        // 如果是基本类型，不遍历
        if (TypeUtils.isPrimitiveOrWrap(object.getClass()))
            return false;

        // 如果是枚举类，不遍历
        if (object.getClass().isEnum())
            return false;

        if (parentIdentityHashMap == null)
            parentIdentityHashMap = new IdentityHashMap<Object, Object>();

        parentIdentityHashMap.put(object, "exist");
        if (Map.class.isAssignableFrom(object.getClass())) { // 对于 map 类型，遍历 map 成员
            for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
                Object itemObj = entry.getValue();
                if (TypeUtils.isPrimitiveOrWrap(itemObj.getClass()))
                    continue;
                if (parentIdentityHashMap.containsKey(itemObj))
                    return true;
                if (isCircleReference(itemObj, parentIdentityHashMap)) ;
                return true;
            }
        } else if (Collection.class.isAssignableFrom(object.getClass())) { // 对于 Collection 类型，遍历集合成员
            Iterator iterator = ((Collection) object).iterator();
            while (iterator.hasNext()) {
                Object itemObj = iterator.next();
                if (TypeUtils.isPrimitiveOrWrap(itemObj.getClass()))
                    continue;
                if (parentIdentityHashMap.containsKey(itemObj))
                    return true;
                if (isCircleReference(itemObj, parentIdentityHashMap))
                    return true;
            }
        } else if (object.getClass().isArray()) { // 数组类型，遍历数组成员
            if (!TypeUtils.isPrimitiveOrWrap(object.getClass().getComponentType())) {
                int len = Array.getLength(object);
                for (int i = 0; i < len; i++) {
                    Object itemObj = Array.get(object, i);
                    if (parentIdentityHashMap.containsKey(itemObj))
                        return true;
                    if (isCircleReference(itemObj, parentIdentityHashMap))
                        return true;
                }
            }
        } else if (!TypeUtils.isPrimitiveOrWrap(object.getClass())) { // 普通对象，遍历其成员
            Map</**fieldName*/String, Field> fieldCacheMap = new HashMap<>();
            parserAllFieldToCache(object.getClass(), fieldCacheMap);
            for (Map.Entry<String, Field> fieldEntry : fieldCacheMap.entrySet()) {
                Field field = fieldEntry.getValue();
                try {
                    field.setAccessible(true);
                    Object itemObj = field.get(object);
                    if (itemObj == null)
                        continue;
                    if (TypeUtils.isPrimitiveOrWrap(itemObj.getClass()))
                        continue;
                    if (parentIdentityHashMap.containsKey(itemObj))
                        return true;
                    if (isCircleReference(itemObj, parentIdentityHashMap))
                        return true;
                } catch (Exception e) {
                    StartConfig.throwableStackPrinter(e);
                    return false;
                }
            }
        }
        parentIdentityHashMap.remove(object);
        return false;
    }

    public static void parserAllFieldToCache(Class<?> clazz, Map</**fieldName*/String, Field> fieldCacheMap) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            String fieldName = field.getName();
            if (!fieldCacheMap.containsKey(fieldName)) {
                fieldCacheMap.put(fieldName, field);
            }
        }
        if (clazz.getSuperclass() != null && clazz.getSuperclass() != Object.class) {
            parserAllFieldToCache(clazz.getSuperclass(), fieldCacheMap);
        }
    }
}